package core;

import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
//import java.util.GregorianCalendar;
//import java.util.HashMap;

import common.TimeSource;
import common.util.DateUtils;
import core.reports.IReportVisitor;

/**
 * Represents an item, which is a specific instance of a given Product
 * 
 * @author Justin
 */
 
public class Item extends ModelObject implements Comparable<Item> {
	private static final long serialVersionUID = -2769570970985191398L;
	/** The type of Product that this item is */
	private Product _product;
	/** The Item Barcode that uniquely identifies this particular Item */
	private Barcode _barcode;
	/** The date when this Item entered the system */
	private Calendar _entryDate;
	/** The date when this Item exited the system */
	private Calendar _exitDate;
	/** A pointer to the ItemManager where this Item is stored */
	private ItemManager _manager;
	/** Whether the Item has been removed*/
	private boolean _isUsed;

	/**
	 * Constructor for an Item object
	 * 
	 * @param product
	 *            The Product to which this Item belongs
	 * @param barcode
	 *            An ItemBarcode which uniquely identifies this specific Item
	 * @param container
	 *            The Container where this Item is located
	 * @throws IllegalArgumentException
	 *             Throws an IllegalArgumentException if any of the parameters
	 *             are invalid
	 */
	public Item(Product product, Barcode barcode)
			throws IllegalArgumentException {
		assert (product != null);
		assert (barcode != null);
		this._product = product;
		this._barcode = barcode;
		this._entryDate = TimeSource.instance().getTime();
		this._isUsed = false;
	}
	
	public Item(Product product, String barcode) throws IllegalArgumentException{
		assert (product != null);
		assert (barcode != null);
		Barcode _barcode = new Barcode(barcode, BarcodeType.ITEM);
		this._product = product;
		this._barcode = _barcode;
		this._entryDate = TimeSource.instance().getTime();
		this._isUsed = false;		
	}

	
	/**
	 * Determine whether a given date is valid.
	 * Dates must follow the format MM/DD/YYYY. They must not be before January 1dst, 2000 nor
	 * in the future.
	 * @param newDate
	 * @return whether the date is valid
	 */
	public static boolean isValidDate(String newDate) {
		Date tempDate;
		try {
			tempDate = DateUtils.parseDate(newDate);
			return isValidDate(tempDate);
		} catch (ParseException e1) {
			return false;
		}
	}
	
	public static boolean isValidDate(Date newDate){
		long jan1st2000 = 946684800000L;
		Date tooOld = new Date(jan1st2000);
		Date now = new Date();
		if(newDate.before(tooOld))return false;
		if(newDate.after(now))return false;
		return true;
	}

	/**
	 * Replace the entry date of the Item
	 * @param newDate
	 */
	public void changeEntryDate(String newDate) {
		if (isValidDate(newDate)) {
			// make a new Calendar object and replace _entryDate with it
			String delim = "/";
			String[] tokens = newDate.split(delim);
			Calendar _newCal = TimeSource.instance().getTime();
			_newCal.set(Integer.parseInt(tokens[2]), Integer.parseInt(tokens[0])-1, 
					Integer.parseInt(tokens[1]));
			_entryDate = _newCal;
		}
	}
	
	public void changeEntryDate(Date newDate){
		if(isValidDate(newDate)){
			Calendar _newCal = Calendar.getInstance();
			_newCal.setTime(newDate);
			_entryDate = _newCal;
		}
	}
	
	/**
	 * Determines whether this item "existed" (i.e. wasn't used) on a certain Date.
	 * @param date the Date to check for.
	 * @return true if the item wasn't used on this date, false otherwise.
	 */
	public boolean existedOnDate(Calendar date){
		Calendar currDate = (Calendar)date.clone();
		DateUtils.setToMidnight(currDate);
		
		Calendar entered = (Calendar)_entryDate.clone();
		DateUtils.setToMidnight(entered);
		
		Calendar used = Calendar.getInstance();
		if (_isUsed)
		{
			used = (Calendar)_exitDate.clone();
			DateUtils.setToMidnight(used);
		}
		
		if (!currDate.before(entered))
		{
			if (!_isUsed)
				return true;
			if (!currDate.after(used))
				return true;
		}
		return false;
	}
	
	/**
	 * 
	 * @return the age of this Item in days
	 */
	public int age(){
		int age = 0;
		Calendar tempEntry = (Calendar)_entryDate.clone();
		setToMidnight(tempEntry);
		
		//if removed
		if(_isUsed){
			//return exit - entry
			Calendar tempExit = (Calendar)_exitDate.clone();
			setToMidnight(tempExit);
			while(tempEntry.before(tempExit)){
				tempEntry.add(Calendar.DAY_OF_MONTH, 1);
				age++;
			}
		}
		else{
			Calendar today = TimeSource.instance().getTime();
			setToMidnight(today);
			//return current date - entry
			while(tempEntry.before(today)){
				tempEntry.add(Calendar.DAY_OF_MONTH, 1);
				age++;
			}
		}
		return age;
	}

	private void setToMidnight(Calendar c)
	{
		c.set(Calendar.HOUR_OF_DAY, 0);
		c.set(Calendar.MINUTE, 0);
		c.set(Calendar.SECOND, 0);
		c.set(Calendar.MILLISECOND, 0);
	}
	
	/**
	 * Determines whether this Item may be moved to newContainer
	 * 
	 * @param newContainer
	 *            The container to which the Item will be moved
	 * @return Whether this Item may be moved to newContainer
	 */
	public boolean canMoveToContainer(Container newContainer) {
		return true;
	}

	/**
	 * @return the Product to which this Item belongs
	 */
	public final Product getProduct() {
		return this._product;
	}

	/**
	 * Gets the amount used by the item
	 * 
	 * @return the amount of an item
	 */
	public Amount getAmount()
	{
		return _product.getSize();
	}
	
	/**
	 * @return the ItemBarcode which uniquely identifies this Item
	 */
	public final Barcode getBarcode() {
		return this._barcode;
	}

	/**
	 * @return the date when this Item entered the system
	 */
	public final Calendar getEntryDate() {
		return this._entryDate;
	}

	/**
	 * @return the date when this Item exited the system
	 */
	public final Calendar getExitDate() {
		if(_isUsed)return this._exitDate;
		return null;
	}
	
	
	/**
	 * Sets when the Item was removed and placed in the Removed Items collection
	 * @param exitDate
	 */
	public void setExitDate(Calendar exitDate){
		this._exitDate = (Calendar) exitDate.clone(); 
		_isUsed = true;
	}
	
	/**
	 * @return whether the Item has been removed
	 */
	public boolean isUsed(){
		return _isUsed;
	}
	
	/**
	 * Set whether the Item has been used
	 * @param used
	 */
	protected void setUsed(boolean used){
		_isUsed = used;
	}
	
	/**
	 * Give the Item a reference to its Item Manager
	 * @pre manager is not null
	 * @param manager
	 */
	protected void setManager(ItemManager manager){
		assert(manager != null);
		this._manager = manager;
	}

	/**
	 * @return the date when this Item will expire
	 */
	public Calendar getExpirationDate() {
		// get the product's shelf life
		int _shelfLife = _product.getShelfLife();
		// get the item's entry date
		Calendar _retCal = (Calendar) _entryDate.clone();
		_retCal.add(Calendar.MONTH, _shelfLife);
		return _retCal;
	}

	/**
	 * @return the Container where this Item is located
	 */
	public final Container getContainer() {
		//get it from the item manager
		return ItemManager.instance().getContainer(this);
		//return this._manager.getContainer(this);
	}
	
	
	/**
	 * Give the Item a reference to its Container
	 * @param container
	 */
	public void setContainer(Container container) {
		ItemManager.instance().setContainer(this, container);
		//this._manager.setContainer(this, container);
	}
	
	/**
	 * 
	 * @return the description of the Product associated with this Item.
	 */
	public String getDescription() {
		return getProduct().getDescription();
	}
	
	/**
	 * 
	 * @return a String representing the Barcode associated with this Item.
	 */
	public String getBarcodeString() {
		return getBarcode().getString();
	}
	
	/**
	 * Accepts a Visitor
	 * @param v the visiting IReportVisitor
	 */
	public void accept(IReportVisitor v) {
		v.visitItem(this);
		
	}

	@Override
	public int compareTo(Item o) {
		return _barcode.compareTo(o.getBarcode());
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result
				+ ((_barcode == null) ? 0 : _barcode.hashCode());
		return result;
	}

	@Override
	public final boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Item other = (Item) obj;
		if (_barcode == null) {
			if (other._barcode != null)
				return false;
		} else if (!_barcode.equals(other._barcode))
			return false;
		return true;
	}

}
